﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using CodeGame.Networking;
using CodeGame.Networking.Clientside;
using CodeGame.Networking.Clientside.ServerClientPackets;

using System.Reflection;
using System.Threading;

using Lidgren.Network;

namespace CodeGame
{
    class NetworkManager
    {
        private string PlayerToken = "";

        private NetClient Client;
        private Thread NetThread;

        private Dictionary<byte, ClientPacket> Packets = new Dictionary<byte, ClientPacket>();

        public NetworkManager()
        {
            NetworkingGlobals.InitConfigs();

            this.Client = new NetClient(NetworkingGlobals.ClientConfig);
            this.Client.Start();

            this.LoadPackets(Assembly.GetEntryAssembly(), "CodeGame.Networking.Clientside.ServerClientPackets");

            this.NetThread = new Thread(new ParameterizedThreadStart(NetworkThread));
            this.NetThread.IsBackground = true;
            this.NetThread.Start(this.Client);
        }

        public void Connect(string host, string nick)
        {
            NetOutgoingMessage message = this.Client.CreateMessage();
            message.Write(nick);
            try
            {
                this.Client.Connect(host, NetworkingGlobals.ServerPort, message);
            }
            catch
            {
                Singletons.Console.Print("Host not Found", Singletons.ErrorText);
            }
            Thread.Sleep(500);
        }

        public void Disconnect()
        {
            this.PlayerToken = "";
            this.Client.Disconnect("Bye");

            if (Singletons.CurrentWorld != null)
            {
                Singletons.CurrentWorld = null;
                Singletons.States.SwitchState(new MenuState());
            }
        }

        public static void NetworkThread(object obj)
        {
            NetClient client = (NetClient)obj;

            while (true)
            {
                NetIncomingMessage message = client.ReadMessage();

                if (message == null)
                {
                    Thread.Sleep(5);
                    continue;
                }

                switch (message.MessageType)
                {
                    case NetIncomingMessageType.Data:
                        Singletons.Network.HandleData(message);
                        break;
                    case NetIncomingMessageType.StatusChanged:
                        Singletons.Console.Print("Network Changed State to: " + message.SenderConnection.Status.ToString(), Singletons.NetworkColor);
                        break;
                    case NetIncomingMessageType.ErrorMessage:
                    case NetIncomingMessageType.VerboseDebugMessage:
                    case NetIncomingMessageType.WarningMessage:
                        Singletons.Console.Print(message.MessageType.ToString() + " : " + message.ReadString(), Singletons.NetworkColor);
                        break;
                    default:
                        break;
                }

                Thread.Sleep(0);
            }
        }

        private void HandleData(NetIncomingMessage message)
        {
            byte type = message.ReadByte();

            if (this.Packets.ContainsKey(type))
            {
                ClientPacket pack = (ClientPacket)(this.Packets[type].Clone());
                pack.sender = null;
                pack.Handle(message);
            }
        }

        public void LoadPackets(Assembly asm, string space)
        {
            foreach (Type item in asm.GetTypes())
            {
                if (item.Namespace == space)
                {
                    object obj = asm.CreateInstance(item.FullName);
                    ClientPacket pack = (ClientPacket)obj;
                    this.Packets.Add((byte)pack.GetID(), pack);
                }
            }
        }

        public void SendMessage(NetOutgoingMessage msg)
        {
            this.Client.SendMessage(msg, NetDeliveryMethod.ReliableOrdered);
        }

        public NetOutgoingMessage CreateMessage(ClientServerNet type)
        {
            if (this.PlayerToken == "")
            {
                Singletons.Console.Print("Not connected to a server", Singletons.ErrorText);
                throw new Exception();
            }
            NetOutgoingMessage msg = this.Client.CreateMessage();
            msg.Write((byte)type);
            msg.Write(this.PlayerToken);
            return msg;
        }

        public void SetToken(string token)
        {
            this.PlayerToken = token;
        }

        public string GetToken()
        {
            return this.PlayerToken;
        }
    }
}
